package by.msokoloff.javaweb2015.dataaccess.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.lang3.Validate;
import org.hibernate.jpa.criteria.OrderImpl;

import by.msokoloff.javaweb2015.dataaccess.AbstractDao;

public abstract class AbstractDaoImpl<Entity> implements AbstractDao<Entity> {

	private EntityManager em;
	private final Class<Entity> entityClass;

	protected EntityManager getEm() {
		return em;
	}

	@PersistenceContext
	protected void setEm(EntityManager em) {
		this.em = em;
	}

	public Class<Entity> getEntityClass() {
		return entityClass;
	}

	protected AbstractDaoImpl(final Class<Entity> entityClass) {
		Validate.notNull(entityClass, "entityClass could not be a null");
		this.entityClass = entityClass;
	}

	@Override
	public Entity getById(Integer id) {
		Validate.notNull(id, "ID could not be a null!");
		return em.find(getEntityClass(), id);
	}

	@Override
	public List<Entity> getAll() {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		cq.from(getEntityClass());
		return em.createQuery(cq).getResultList();
	}

	@Override
	public List<Entity> getAll(SingularAttribute<? super Entity, ?> attr, boolean ascending, int startRecord, int pageSize) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		Root<Entity> root = cq.from(getEntityClass());
		cq.orderBy(new OrderImpl(root.get(attr), ascending));
		TypedQuery<Entity> q = em.createQuery(cq);
		q.setFirstResult(startRecord);
		q.setMaxResults(pageSize);
		return q.getResultList();
	}

	@Override
	public List<Entity> getByField(SingularAttribute<? super Entity, ?> attr, Object val) {
		Validate.notNull(val, "Search attributes can't be empty. Attribute: " + attr.getName());
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		Root<Entity> root = cq.from(getEntityClass());
		cq.select(root).distinct(true);
		cq.where(cb.equal(root.get(attr), val));
		return em.createQuery(cq).getResultList();
	}

	@Override
	public List<Entity> getAll(SingularAttribute<? super Entity, ?> filterAttr, Object val, SingularAttribute<? super Entity, ?> attr, boolean ascending, int startRecord, int pageSize) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		Root<Entity> root = cq.from(getEntityClass());
		cq.where(cb.equal(root.get(filterAttr), val));
		cq.orderBy(new OrderImpl(root.get(attr), ascending));
		TypedQuery<Entity> q = em.createQuery(cq);
		q.setFirstResult(startRecord);
		q.setMaxResults(pageSize);
		return q.getResultList();
	}

	@Override
	public Entity create(Entity entity) {
		em.persist(entity);
		return entity;
	}

	@Override
	public Entity update(Entity entity) {
		entity = em.merge(entity);
		em.flush();
		return entity;
	}

	@Override
	public void delete(Integer id) {
		Validate.notNull(id, "ID could not be a null!");
		em.remove(em.find(getEntityClass(), id));
	}

	@Override
	public void delete(List<Integer> ids) {
		String sqlQuery = "delete from %s e where e.id in (:ids)";
		Query query = em.createQuery(String.format(sqlQuery,
				entityClass.getSimpleName()));
		query.setParameter("ids", ids);
		query.executeUpdate();
		em.flush();
	}

	@Override
	public void deleteAll() {
		final Query q = em.createQuery("delete from " + getEntityClass().getSimpleName());
		q.executeUpdate();
		em.flush();
	}

	@Override
	public Long getCount() {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		Root<Entity> root = cq.from(getEntityClass());
		cq.select(cb.count(root));
		TypedQuery<Long> q = getEm().createQuery(cq);
		return q.getSingleResult();
	}

	@Override
	public Long getCountByField(SingularAttribute<? super Entity, ?> attr, Object val) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		Root<Entity> root = cq.from(getEntityClass());
		cq.where(cb.equal(root.get(attr), val));
		cq.select(cb.count(root));
		TypedQuery<Long> q = getEm().createQuery(cq);
		return q.getSingleResult();
	}
	
	@Override
	public List<Entity> getStartingWith(SingularAttribute<? super Entity, String> field, String val, Integer max) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		Root<Entity> root = cq.from(getEntityClass());
		cq.where(cb.like(cb.lower(root.<String>get(field)), val.toLowerCase() + "%"));
		cq.orderBy(new OrderImpl(root.get(field), true));
		TypedQuery<Entity> q = em.createQuery(cq);
		q.setMaxResults(max);
		return q.getResultList();
	}

	@Override
	public List<Entity> getContaining(SingularAttribute<? super Entity, String> field, String val, Integer max) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(getEntityClass());
		Root<Entity> root = cq.from(getEntityClass());
		cq.where(cb.like(cb.lower(root.<String>get(field)), "%" + val.toLowerCase() + "%"));
		cq.orderBy(new OrderImpl(root.get(field), true));
		TypedQuery<Entity> q = em.createQuery(cq);
		q.setMaxResults(max);
		return q.getResultList();
	}
	
}
